/* JOrbis
 * Copyright (C) 2000 ymnk, JCraft,Inc.
 *  
 * Written by: 2000 ymnk<ymnk@jcraft.com>
 *   
 * Many thanks to 
 *   Monty <monty@xiph.org> and 
 *   The XIPHOPHORUS Company http://www.xiph.org/ .
 * JOrbis has been based on their awesome works, Vorbis codec.
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
   
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package com.jcraft.jorbis;

import com.jcraft.jogg.Buffer;

class Floor0 extends FuncFloor{

  void pack(Object i, Buffer opb){
    InfoFloor0 info=(InfoFloor0)i;
    opb.write(info.order,8);
    opb.write(info.rate,16);
    opb.write(info.barkmap,16);
    opb.write(info.ampbits,6);
    opb.write(info.ampdB,8);
    opb.write(info.numbooks-1,4);
    for(int j=0;j<info.numbooks;j++)
      opb.write(info.books[j],8);
  }

  Object unpack(Info vi , Buffer opb){
    InfoFloor0 info=new InfoFloor0();
    info.order=opb.read(8);
    info.rate=opb.read(16);
    info.barkmap=opb.read(16);
    info.ampbits=opb.read(6);
    info.ampdB=opb.read(8);
    info.numbooks=opb.read(4)+1;
  
    if((info.order<1)||
       (info.rate<1)||
       (info.barkmap<1)||
       (info.numbooks<1)){
      //free_info(info);
      return(null);
    }

    for(int j=0;j<info.numbooks;j++){
      info.books[j]=opb.read(8);
      if(info.books[j]<0 || info.books[j]>=vi.books){
	//free_info(info);
	return(null);
      }
    }
    return(info);  
//  err_out:
//    free_info(info);
//    return(NULL);
  }
  Object look(DspState vd, InfoMode mi, Object i){
    float scale;
    Info vi=vd.vi;
    InfoFloor0 info=(InfoFloor0)i;
    LookFloor0 look=new LookFloor0();
    look.m=info.order;
    look.n=vi.blocksizes[mi.blockflag]/2;
    look.ln=info.barkmap;
    look.vi=info;
    look.lpclook.init(look.ln,look.m);

    // we choose a scaling constant so that:
    //  floor(bark(rate/2-1)*C)=mapped-1
    // floor(bark(rate/2)*C)=mapped
    scale=look.ln/toBARK((float)(info.rate/2.));

    // the mapping from a linear scale to a smaller bark scale is
    // straightforward.  We do *not* make sure that the linear mapping
    // does not skip bark-scale bins; the decoder simply skips them and
    // the encoder may do what it wishes in filling them.  They're
    // necessary in some mapping combinations to keep the scale spacing
    // accurate
    look.linearmap=new int[look.n];
    for(int j=0;j<look.n;j++){
      int val=(int)Math.floor(toBARK((float)((info.rate/2.)/look.n*j)) 
		              *scale); // bark numbers represent band edges
      if(val>=look.ln)val=look.ln; // guard against the approximation
      look.linearmap[j]=val;
    }
    return look;
  }

  static float toBARK(float f){
    return (float)(13.1*Math.atan(.00074*(f))+2.24*Math.atan((f)*(f)*1.85e-8)+1e-4*(f));
  }

  Object state(Object i){
    EchstateFloor0 state=new EchstateFloor0();
    InfoFloor0 info=(InfoFloor0)i;

    // a safe size if usually too big (dim==1)
    state.codewords=new int[info.order];
    state.curve=new float[info.barkmap];
    state.frameno=-1;
    return(state);
  }
  void free_info(Object i){}
  void free_look(Object i){}
  void free_state(Object vs){}
  int forward(Block vb, Object i,  float[] in, float[] out, Object vs){return 0;}

  float[] lsp=null;    
  int inverse(Block vb, Object i, float[] out){
    //System.err.println("Floor0.inverse "+i.getClass()+"]");
    LookFloor0 look=(LookFloor0)i;
    InfoFloor0 info=look.vi;
    int ampraw=vb.opb.read(info.ampbits);
    if(ampraw>0){ // also handles the -1 out of data case
      int maxval=(1<<info.ampbits)-1;
      float amp=(float)ampraw/maxval*info.ampdB;
      int booknum=vb.opb.read(ilog(info.numbooks));

      if(booknum!=-1 && booknum<info.numbooks){

        synchronized(this){ 
        if(lsp==null||lsp.length<look.m){
          lsp=new float[look.m];
        }	  
        else{
          for(int j=0; j<look.m; j++)lsp[j]=0.f;
        }

	CodeBook b=vb.vd.fullbooks[info.books[booknum]];
	float last=0.f;

	//memset(out,0,sizeof(float)*look->m);
        for(int j=0; j<look.m; j++)out[j]=0.0f;

        for(int j=0;j<look.m;j+=b.dim){
	  if(b.decodevs(lsp, j, vb.opb, 1, -1)==-1){
	    //goto eop;
	    // memset(out,0,sizeof(float)*look->n);
            for(int k=0; k<look.n; k++)out[k]=0.0f;
	    return(0);
	  }
	}
	for(int j=0;j<look.m;){
	  for(int k=0;k<b.dim;k++,j++)lsp[j]+=last;
	  last=lsp[j-1];
	}
	// take the coefficients back to a spectral envelope curve
	/*
	lsp_to_lpc(out,out,look.m); 
	lpc_to_curve(out,out,amp,look,"",0);
	for(int j=0;j<look.n;j++){
	  out[j]=fromdB(out[j]-info.ampdB);
	}
	*/
	Lsp.lsp_to_curve(out,look.linearmap,look.n,look.ln,                 
			 lsp,look.m,amp,info.ampdB);    

	return(1);
	}
      }
    }
//  eop:
//    memset(out,0,sizeof(float)*look->n);
    return(0);
  }

  Object inverse1(Block vb, Object i, Object memo){
    //System.err.println("Floor0.inverse "+i.getClass()+"]");
    LookFloor0 look=(LookFloor0)i;
    InfoFloor0 info=look.vi;
    float[] lsp=null;
    if(memo instanceof float[]){
      lsp=(float[])memo;
    }

    int ampraw=vb.opb.read(info.ampbits);
    if(ampraw>0){ // also handles the -1 out of data case
      int maxval=(1<<info.ampbits)-1;
      float amp=(float)ampraw/maxval*info.ampdB;
      int booknum=vb.opb.read(ilog(info.numbooks));

      if(booknum!=-1 && booknum<info.numbooks){
	CodeBook b=vb.vd.fullbooks[info.books[booknum]];
	float last=0.f;

        if(lsp==null||lsp.length<look.m+1){
          lsp=new float[look.m+1];
        }	  
        else{
          for(int j=0; j<lsp.length; j++)lsp[j]=0.f;
        }

        for(int j=0;j<look.m;j+=b.dim){
	  if(b.decodev_set(lsp, j, vb.opb, b.dim)==-1){
	    //goto eop;
	    return(null);
	  }
	}

	for(int j=0;j<look.m;){
	  for(int k=0;k<b.dim;k++,j++)lsp[j]+=last;
	  last=lsp[j-1];
	}
        lsp[look.m]=amp;
	return(lsp);
      }
    }
//  eop:
    return(null);
  }

  int inverse2(Block vb, Object i, Object memo, float[] out){
    //System.err.println("Floor0.inverse "+i.getClass()+"]");
    LookFloor0 look=(LookFloor0)i;
    InfoFloor0 info=look.vi;

    if(memo!=null){
      float[] lsp=(float[])memo;
      float amp=lsp[look.m];

      Lsp.lsp_to_curve(out,look.linearmap,look.n,look.ln,                 
		       lsp,look.m,amp,info.ampdB);    
      return(1);
    }
//  eop:
//    memset(out,0,sizeof(float)*look->n);
    for(int j=0; j<look.n; j++){
      out[j]=0.f;
    } 
    return(0);
  }

  static float fromdB(float x){
    return (float)(Math.exp((x)*.11512925));
  }
  private static int ilog(int v){
    int ret=0;
    while(v!=0){
      ret++;
      v>>>=1;
    }
    return(ret);
  }

  static void lsp_to_lpc(float[] lsp, float[] lpc, int m){ 
    int i,j,m2=m/2;
    float[] O=new float[m2];
    float[] E=new float[m2];
    float A;
    float[] Ae=new float[m2+1];
    float[] Ao=new float[m2+1];
    float B;
    float[] Be=new float[m2];
    float[] Bo=new float[m2];
    float temp;

    // even/odd roots setup
    for(i=0;i<m2;i++){
      O[i]=(float)(-2.*Math.cos(lsp[i*2]));
      E[i]=(float)(-2.*Math.cos(lsp[i*2+1]));
    }

    // set up impulse response
    for(j=0;j<m2;j++){
      Ae[j]=0.f;
      Ao[j]=1.f;
      Be[j]=0.f;
      Bo[j]=1.f;
    }
    Ao[j]=1.f;
    Ae[j]=1.f;

    // run impulse response
    for(i=1;i<m+1;i++){
      A=B=0.f;
      for(j=0;j<m2;j++){
	temp=O[j]*Ao[j]+Ae[j];
	Ae[j]=Ao[j];
	Ao[j]=A;
	A+=temp;

	temp=E[j]*Bo[j]+Be[j];
	Be[j]=Bo[j];
	Bo[j]=B;
	B+=temp;
      }
      lpc[i-1]=(A+Ao[j]+B-Ae[j])/2;
      Ao[j]=A;
      Ae[j]=B;
    }
  }

  static void lpc_to_curve(float[] curve, float[] lpc,float amp,
			   LookFloor0 l, String name, int frameno){
    // l->m+1 must be less than l->ln, but guard in case we get a bad stream
    float[] lcurve=new float[Math.max(l.ln*2,l.m*2+2)];

    if(amp==0){
      //memset(curve,0,sizeof(float)*l->n);
      for(int j=0; j<l.n; j++)curve[j]=0.0f;
      return;
    }
    l.lpclook.lpc_to_curve(lcurve,lpc,amp);

    for(int i=0;i<l.n;i++)curve[i]=lcurve[l.linearmap[i]];
  }
}

class InfoFloor0{
  int order;
  int rate;
  int barkmap;

  int   ampbits;
  int   ampdB;

  int   numbooks; // <= 16
  int[]  books=new int[16];
}

class LookFloor0{
  int n;
  int ln;
  int m;
  int[] linearmap;

  InfoFloor0 vi;
  Lpc lpclook=new Lpc();
}

class EchstateFloor0{
  int[] codewords;
  float[] curve;
  long frameno;
  long codes;
}
